﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nature.Data.CkMarkup
{
    public partial class CkReaction : IChemicalReaction
    {
        double? m_fwdOrder;
        double? m_revOrder;

        internal CkReactionSet ReactionSet { get; set; }

        public object[] ActualActivationEnergyUnits
        {
            get
            {
                return ReactionSet != null
                    ? ReactionSet.ActualActivationEnergyUnits
                    : CkReactionSet.DefaultActivationEnergyUnits;
            }
        }

        public Nature.EnergyUnits ActualEnergyUnits
        {
            get
            {
                return ReactionSet != null
                    ? ReactionSet.ActualEnergyUnits
                    : CkReactionSet.DefaultEnergyUnits;
            }
        }

        public Nature.MolarAmountUnits ActualMolarAmountUnits
        {
            get
            {
                return ReactionSet != null
                    ? ReactionSet.ActualMolarAmountUnits
                    : CkReactionSet.DefaultMolarAmountUnits;
            }
        }

        public Nature.LengthUnits ActualLengthUnits
        {
            get
            {
                return ReactionSet != null
                    ? ReactionSet.ActualLengthUnits
                    : CkReactionSet.DefaultLengthUnits;
            }
        }

        public Nature.TimeUnits ActualTimeUnits
        {
            get
            {
                return ReactionSet != null
                    ? ReactionSet.ActualTimeUnits
                    : CkReactionSet.DefaultTimeUnits;
            }
        }


        public IEnumerable<CkReagent> FwdReagents
        {
            get { return this.ReactionEquation.FwdReaction.Reagents; }
        }

        public IEnumerable<CkReagent> RevReagents
        {
            get { return this.ReactionEquation.RevReaction.Reagents; }
        }

        public double FwdOrder 
        {
            get 
            {
                if (m_fwdOrder == null)
                {
                    double order =
                        (from r in this.FwdReagents
                         group r by r.ReagentID into g
                         let ford = (this.Ford == null ? null : this.Ford.SingleOrDefault(i => i.SpeciesID == g.Key))
                         select (ford != null ? ford.Order : g.Sum(i => i.Coefficient.HasValue ? i.Coefficient.Value : 1d))).Sum();
                    if (this.ThirdBody != null && this.Low == null)
                    {
                        order += 1d;
                    }
                    m_fwdOrder = order;                    
                }
                return m_fwdOrder.Value;
            }
        }

        public double RevOrder
        {
            get
            {
                if (m_revOrder == null)
                {
                    double order =
                        (from r in this.RevReagents
                         group r by r.ReagentID into g
                         let rord = (this.Rord == null ? null : this.Rord.SingleOrDefault(i => i.SpeciesID == g.Key))
                         select (rord != null ? rord.Order : g.Sum(i => i.Coefficient.HasValue ? i.Coefficient.Value : 1d))).Sum();
                    if (this.ThirdBody != null && this.Low == null)
                    {
                        order += 1d;
                    }
                    m_revOrder = order;
                }
                return m_revOrder.Value;
            }
        }

        public string ThirdBody
        {
            get { return this.ReactionEquation.ThirdBody; }
        }

        public bool HasThirdBody
        {
            get
            {
                return (!string.IsNullOrWhiteSpace(this.ThirdBody));
            }
        }

        public bool HasReagent(string reagentID)
        {
            return FwdReagents.Any(i => i.ReagentID == reagentID) ||
                RevReagents.Any(i => i.ReagentID == reagentID);
        }

        public double GetReagentProduction(string reagentID)
        {
            return RevReagents.GetReagentStoichiometricCoefficient(reagentID) -
                FwdReagents.GetReagentStoichiometricCoefficient(reagentID);
        }

        public bool IsReversible
        {
            get { return this.ReactionEquation.IsReversible; }
        }

        public IEnumerable<CkReagent> Reagents
        {
            get { return FwdReagents.Union(RevReagents);  }
        }

        public int DistinctReagentsCount
        {
            get 
            {
                return Reagents
                    .Select(i => i.ReagentID)
                    .Distinct()
                    .Count();
            }
        }


        #region IChemicalReaction Members

        private ArrheniusParameters AdjustUnits(ArrheniusParameters coef, double order)
        {
            var actEnergyUnits = this.ActualActivationEnergyUnits;
            var typedArrheniusPrefactor = new ArrheniusPrefactor(
                    coef.A, 
                    order, 
                    this.ActualLengthUnits, 
                    this.ActualMolarAmountUnits, 
                    this.ActualTimeUnits);

            if (actEnergyUnits.Contains(Nature.TemperatureUnits.Kelvin))
            {
                return new ArrheniusParameters
                {
                    A = typedArrheniusPrefactor,
                    B = coef.B,
                    C = coef.C
                };
            }
            else
            {
                var typedActivationEnergy = new MolarEnergy(coef.C,
                    actEnergyUnits.OfType<Nature.EnergyUnits>().Single(),
                    actEnergyUnits.OfType<Nature.MolarAmountUnits>().Single());
                return new ArrheniusParameters 
                {
                    A = typedArrheniusPrefactor,
                    B = coef.B,
                    C = typedActivationEnergy / Constants.Rgas // [K]
                };
            }            
        }

        bool IChemicalReaction.IsReversible
        {
            get { return this.IsReversible; }
        }

        string IChemicalReaction.ThirdBodyID
        {
            get { return this.ThirdBody; }
        }

        IEnumerable<ChemicalReagent> IChemicalReaction.Reagents
        {
            get 
            {
                var @fwdReagents = this.FwdReagents.ToList();
                var @revReagents = this.RevReagents.ToList();
                var reagents =
                    (from speciesID in @fwdReagents.Union(@revReagents).Select(i => i.ReagentID).Distinct()
                    select new ChemicalReagent { SpeciesID = speciesID }).ToList();
                foreach (var reagent in reagents)
                {
                    var fwd = @fwdReagents.Where(i=> i.ReagentID == reagent.SpeciesID);
                    foreach (var item in fwd)
                    {
                        reagent.FwdCoefficient += item.Coefficient.HasValue ? item.Coefficient.Value : 1.0d;
                    }
                    var ford = this.Ford.SingleOrDefault(i => i.SpeciesID == reagent.SpeciesID);
                    reagent.FwdOrder = (ford == null ? reagent.FwdCoefficient : ford.Order); 
                }
                foreach (var reagent in reagents)
                {
                    var rev = @revReagents.Where(i => i.ReagentID == reagent.SpeciesID);
                    foreach (var item in rev)
                    {
                        reagent.RevCoefficient += item.Coefficient.HasValue ? item.Coefficient.Value : 1.0d;
                    }
                    var rord = this.Rord.SingleOrDefault(i => i.SpeciesID == reagent.SpeciesID);
                    reagent.RevOrder = (rord == null ? reagent.RevCoefficient : rord.Order);
                }
                return reagents;
            }
        }

        IEnumerable<Enhanced> IChemicalReaction.Enhanced
        {
            get 
            {
                if( Enhanced == null){ return Enumerable.Empty<Enhanced>(); }
                var enhanced = 
                    (from e in this.Enhanced 
                     select new Enhanced { SpeciesID = e.ReagentID, Coefficient = e.Coefficient }).ToList();
                var duplicates =
                    from e in enhanced
                    group e by e.SpeciesID into g
                    where g.Count() > 1
                    select g.Key;
                if (duplicates.Any())
                {
                    throw new InvalidOperationException();
                }
                return enhanced;
            }
        }

        ArrheniusParameters IChemicalReaction.Fwd
        {
            get 
            {
                return AdjustUnits(new ArrheniusParameters 
                {
                    A = this.FwdConstant.CoefA, 
                    B = this.FwdConstant.CoefB, 
                    C = this.FwdConstant.CoefC 
                }, FwdOrder);
            }
        }

        ArrheniusParameters IChemicalReaction.Rev
        {
            get
            {
                if (this.RevConstant == null) { return null; }
                return AdjustUnits(new ArrheniusParameters
                {
                    A = this.RevConstant.CoefA,
                    B = this.RevConstant.CoefB,
                    C = this.RevConstant.CoefC
                }, RevOrder);
            }
        }

        ArrheniusParameters IChemicalReaction.Low
        {
            get
            {
                if (this.Low == null) { return null; }
                return AdjustUnits( new ArrheniusParameters
                {
                    A = this.Low.CoefA,
                    B = this.Low.CoefB,
                    C = this.Low.CoefC
                }, FwdOrder + 1d);
            }
        }

        TroeParameters IChemicalReaction.Troe
        {
            get 
            {
                
                return this.Troe == null ? null : 
                    new TroeParameters 
                {
                    A = this.Troe.CoefA,
                    B = this.Troe.CoefB,
                    C = this.Troe.CoefC,
                    D = this.Troe.CoefD
                };
            }
        }

        SriParameters IChemicalReaction.Sri
        {
            get
            {
                return this.Sri == null ? null :
                    new SriParameters
                {
                    A = this.Sri.CoefA,
                    B = this.Sri.CoefB,
                    C = this.Sri.CoefC,
                    D = this.Sri.CoefD,
                    E = this.Sri.CoefE
                };
            }
        }

        #endregion
    }
}
